/*

   FDI to raw bit stream converter
   Copyright (c) 2001 by Toni Wilen <twilen@arabuusimiehet.com>
   FDI 2.0 support
   Copyright (c) 2003-2004 by Toni Wilen <twilen@arabuusimiehet.com>
   and Vincent Joguin

   FDI format created by Vincent "ApH" Joguin

   Tiny changes - function type fixes, multiple drives, addition of
   get_last_head and C++ callability - by Thomas Harte, 2001,
   T.Harte@excite.co.uk


   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the Free
   Software Foundation; either version 2 of the License, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
   more details.

   You should have received a copy of the GNU General Public License along
   with this program; if not, write to the Free Software Foundation, Inc.,
   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA

 */

#include "sysconfig.h"

#ifdef FDI2RAW

    #include "fdi2raw.h"
    #include "crc32.h"
    #include "uae.h"
    #include "zfile.h"

    #if FDI_DEBUG_DEF
static TCHAR* datalog(byte* src, int len)
{
    static TCHAR buf[1000];
    static int offset;
    int i = 0, offset2;

    offset2 = offset;
    buf[offset++] = '\'';
    while (len--)
    {
        _stprintf(buf + offset, L"%02X", src[i]);
        offset += 2;
        i++;
        if (i > 10)
            break;
    }
    buf[offset++] = '\'';
    buf[offset++] = 0;
    if (offset >= 900)
        offset = 0;
    return buf + offset2;
}
    #else
static TCHAR* datalog(byte* src, int len)
{
    return L"";
}
    #endif

    #if FDI_DEBUG_DEF
        #define debuglog Logger::Write
    #else
        #define debuglog
    #endif
    #if FDI_VERBOSE_DEF
        #define outlog Logger::Write
    #else
        #define outlog
    #endif

static int fdi_allocated;

// #ifdef FDI_DEBUG_DEF
// static void fdi_free(void* p)
// {
//     int size;
//     if (!p)
//         return;
//     size = ((int*)p)[-1];
//     fdi_allocated -= size;
//     Logger::Write(L"%d freed (%d)\n", size, fdi_allocated);
//     free((int*)p - 1);
// }
// static void * fdi_malloc(int size)
// {
//     void* p = xmalloc(size + sizeof(int));
//     ((int*)p)[0] = size;
//     fdi_allocated += size;
//     Logger::Write(L"%d allocated (%d)\n", size, fdi_allocated);
//     return (int*)p + 1;
// }
// #else
    #define fdi_free free
    #define fdi_malloc xmalloc
// #endif

    #define MAX_SRC_BUFFER 4194304
    #define MAX_DST_BUFFER 40000
    #define MAX_MFM_SYNC_BUFFER 60000
    #define MAX_TIMING_BUFFER 400000
    #define FDI_MAX_TRACKS 168
struct fdi_cache
{
    uint* avgp, * minp, * maxp;
    byte* idxp;
    int avg_free, idx_free, min_free, max_free;
    uint totalavg, pulses, maxidx, indexoffset;
    int weakbits;
    int lowlevel;
};
struct FDI
{
    byte* track_src_buffer;
    byte* track_src;
    int track_src_len;
    byte* track_dst_buffer;
    byte* track_dst;
    ushort* track_dst_buffer_timing;
    byte track_len;
    byte track_type;
    int current_track;
    int last_track;
    int last_head;
    int rotation_speed;
    int bit_rate;
    int disk_type;
    int write_protect;
    int reversed_side;
    int err;
    byte header[2048];
    int track_offsets[FDI_MAX_TRACKS];
    struct zfile* file;
    int out;
    int mfmsync_offset;
    int* mfmsync_buffer;
    /* sector described only */
    int index_offset;
    int encoding_type;
    /* bit handling */
    int nextdrop;
    struct fdi_cache cache[FDI_MAX_TRACKS];
};

    #define get_u32(x) ((((x)[0]) << 24) | (((x)[1]) << 16) | (((x)[2]) << 8) | ((x)[3]))
    #define get_u24(x) ((((x)[0]) << 16) | (((x)[1]) << 8) | ((x)[2]))
static __forceinline void put_u32(byte* d, uint v)
{
    d[0] = v >> 24;
    d[1] = v >> 16;
    d[2] = v >> 8;
    d[3] = v;
}

struct Node
{
    ushort v;
    struct Node* left;
    struct Node* right;
};

static byte temp, temp2;

static byte* expand_tree(byte* stream, Node* node)
{
    if (temp & temp2)
    {
        fdi_free(node->left);
        node->left = 0;
        fdi_free(node->right);
        node->right = 0;
        temp2 >>= 1;
        if (!temp2)
        {
            temp = *stream++;
            temp2 = 0x80;
        }
        return stream;
    }
    else
    {
        byte* stream_temp;
        temp2 >>= 1;
        if (!temp2)
        {
            temp = *stream++;
            temp2 = 0x80;
        }
        node->left = fdi_malloc(Node, 1);
        memset(node->left, 0, sizeof(Node));
        stream_temp = expand_tree(stream, node->left);
        node->right = fdi_malloc(Node, 1);
        memset(node->right, 0, sizeof(Node));
        return expand_tree(stream_temp, node->right);
    }
}

static byte* values_tree8(byte* stream, Node* node)
{
    if (node->left == 0)
    {
        node->v = *stream++;
        return stream;
    }
    else
    {
        byte* stream_temp = values_tree8(stream, node->left);
        return values_tree8(stream_temp, node->right);
    }
}

static byte* values_tree16(byte* stream, Node* node)
{
    if (node->left == 0)
    {
        ushort high_8_bits = (*stream++) << 8;
        node->v = high_8_bits | (*stream++);
        return stream;
    }
    else
    {
        byte* stream_temp = values_tree16(stream, node->left);
        return values_tree16(stream_temp, node->right);
    }
}

static void free_nodes(Node* node)
{
    if (node)
    {
        free_nodes(node->left);
        free_nodes(node->right);
        fdi_free(node);
    }
}

static uint sign_extend16(uint v)
{
    if (v & 0x8000)
        v |= 0xffff0000;
    return v;
}

static uint sign_extend8(uint v)
{
    if (v & 0x80)
        v |= 0xffffff00;
    return v;
}

static void fdi_decode(byte* stream, int size, byte* out)
{
    int i;
    byte sign_extend, sixteen_bit, sub_stream_shift;
    Node root;
    Node* current_node;

    memset(out, 0, size * 4);
    sub_stream_shift = 1;
    while (sub_stream_shift)
    {
        // sub-stream header decode
        sign_extend = *stream++;
        sub_stream_shift = sign_extend & 0x7f;
        sign_extend &= 0x80;
        sixteen_bit = (*stream++) & 0x80;

        // huffman tree architecture decode
        temp = *stream++;
        temp2 = 0x80;
        stream = expand_tree(stream, &root);
        if (temp2 == 0x80)
            stream--;

        // huffman output values	decode
        if (sixteen_bit)
            stream = values_tree16(stream, &root);
        else
            stream = values_tree8(stream, &root);

        // sub-stream data decode
        temp2 = 0;
        for (i = 0; i < size; i++)
        {
            uint v;
            byte decode = 1;
            current_node = &root;
            while (decode)
            {
                if (current_node->left == 0)
                {
                    decode = 0;
                }
                else
                {
                    temp2 >>= 1;
                    if (!temp2)
                    {
                        temp2 = 0x80;
                        temp = *stream++;
                    }
                    if (temp & temp2)
                        current_node = current_node->right;
                    else
                        current_node = current_node->left;
                }
            }
            v = ((uint*)out)[i];
            if (sign_extend)
            {
                if (sixteen_bit)
                    v |= sign_extend16(current_node->v) << sub_stream_shift;
                else
                    v |= sign_extend8(current_node->v) << sub_stream_shift;
            }
            else
            {
                v |= current_node->v << sub_stream_shift;
            }
            ((uint*)out)[i] = v;
        }
        free_nodes(root.left);
        free_nodes(root.right);
    }
}

static int decode_raw_track(FDI* fdi)
{
    int size = get_u32(fdi->track_src);
    memcpy(fdi->track_dst, fdi->track_src, (size + 7) >> 3);
    fdi->track_src += (size + 7) >> 3;
    return size;
}

/* unknown track */
static void zxx(FDI* fdi)
{
    outlog(L"track %d: unknown track type 0x%02X\n", fdi->current_track, fdi->track_type);
    //  return -1;
}
/* unsupported track */
static void zyy(FDI* fdi)
{
    outlog(L"track %d: unsupported track type 0x%02X\n", fdi->current_track, fdi->track_type);
    //  return -1;
}
/* empty track */
static void track_empty(FDI* fdi)
{
    //  return 0;
}

/* unknown sector described type */
static void dxx(FDI* fdi)
{
    outlog(L"\ntrack %d: unknown sector described type 0x%02X\n", fdi->current_track, fdi->track_type);
    fdi->err = 1;
}
/* unsupported sector described type */
static void dyy(FDI* fdi)
{
    outlog(L"\ntrack %d: unsupported sector described 0x%02X\n", fdi->current_track, fdi->track_type);
    fdi->err = 1;
}
/* add position of mfm sync bit */
static void add_mfm_sync_bit(FDI* fdi)
{
    if (fdi->nextdrop)
    {
        fdi->nextdrop = 0;
        return;
    }
    fdi->mfmsync_buffer[fdi->mfmsync_offset++] = fdi->out;
    if (fdi->out == 0)
    {
        outlog(L"illegal position for mfm sync bit, offset=%d\n", fdi->out);
        fdi->err = 1;
    }
    if (fdi->mfmsync_offset >= MAX_MFM_SYNC_BUFFER)
    {
        fdi->mfmsync_offset = 0;
        outlog(L"mfmsync buffer overflow\n");
        fdi->err = 1;
    }
    fdi->out++;
}

    #define BIT_BYTEOFFSET ((fdi->out) >> 3)
    #define BIT_BITOFFSET (7 - ((fdi->out) & 7))

/* add one bit */
static void bit_add(FDI* fdi, int bit)
{
    if (fdi->nextdrop)
    {
        fdi->nextdrop = 0;
        return;
    }
    fdi->track_dst[BIT_BYTEOFFSET] &= ~(1 << BIT_BITOFFSET);
    if (bit)
        fdi->track_dst[BIT_BYTEOFFSET] |= (1 << BIT_BITOFFSET);
    fdi->out++;
    if (fdi->out >= MAX_DST_BUFFER * 8)
    {
        outlog(L"destination buffer overflow\n");
        fdi->err = 1;
        fdi->out = 1;
    }
}
/* add bit and mfm sync bit */
static void bit_mfm_add(FDI* fdi, int bit)
{
    add_mfm_sync_bit(fdi);
    bit_add(fdi, bit);
}
/* remove following bit */
static void bit_drop_next(FDI* fdi)
{
    if (fdi->nextdrop > 0)
    {
        outlog(L"multiple bit_drop_next() called");
    }
    else if (fdi->nextdrop < 0)
    {
        fdi->nextdrop = 0;
        debuglog(L":DNN:");
        return;
    }
    debuglog(L":DN:");
    fdi->nextdrop = 1;
}

/* ignore next bit_drop_next() */
static void bit_dedrop(FDI* fdi)
{
    if (fdi->nextdrop)
    {
        outlog(L"bit_drop_next called before bit_dedrop");
    }
    fdi->nextdrop = -1;
    debuglog(L":BDD:");
}

/* add one byte */
static void byte_add(FDI* fdi, byte v)
{
    int i;
    for (i = 7; i >= 0; i--)
        bit_add(fdi, v & (1 << i));
}
/* add one word */
static void word_add(FDI* fdi, ushort v)
{
    byte_add(fdi, (byte)(v >> 8));
    byte_add(fdi, (byte)v);
}
/* add one byte and mfm encode it */
static void byte_mfm_add(FDI* fdi, byte v)
{
    int i;
    for (i = 7; i >= 0; i--)
        bit_mfm_add(fdi, v & (1 << i));
}
/* add multiple bytes and mfm encode them */
static void bytes_mfm_add(FDI* fdi, byte v, int len)
{
    int i;
    for (i = 0; i < len; i++)
        byte_mfm_add(fdi, v);
}
/* add one mfm encoded word and re-mfm encode it */
static void word_post_mfm_add(FDI* fdi, ushort v)
{
    int i;
    for (i = 14; i >= 0; i -= 2)
        bit_mfm_add(fdi, v & (1 << i));
}

/* bit 0 */
static void s00(FDI* fdi)
{
    bit_add(fdi, 0);
}
/* bit 1*/
static void s01(FDI* fdi)
{
    bit_add(fdi, 1);
}
/* 4489 */
static void s02(FDI* fdi)
{
    word_add(fdi, 0x4489);
}
/* 5224 */
static void s03(FDI* fdi)
{
    word_add(fdi, 0x5224);
}
/* mfm sync bit */
static void s04(FDI* fdi)
{
    add_mfm_sync_bit(fdi);
}
/* RLE MFM-encoded data */
static void s08(FDI* fdi)
{
    int bytes = *fdi->track_src++;
    byte b = *fdi->track_src++;
    if (bytes == 0)
        bytes = 256;
    debuglog(L"s08:len=%d,data=%02X", bytes, b);
    while (bytes--)
        byte_add(fdi, b);
}
/* RLE MFM-decoded data */
static void s09(FDI* fdi)
{
    int bytes = *fdi->track_src++;
    byte b = *fdi->track_src++;
    if (bytes == 0)
        bytes = 256;
    bit_drop_next(fdi);
    debuglog(L"s09:len=%d,data=%02X", bytes, b);
    while (bytes--)
        byte_mfm_add(fdi, b);
}
/* MFM-encoded data */
static void s0a(FDI* fdi)
{
    int i, bits = (fdi->track_src[0] << 8) | fdi->track_src[1];
    byte b;
    fdi->track_src += 2;
    debuglog(L"s0a:bits=%d,data=%s", bits, datalog(fdi->track_src, (bits + 7) / 8));
    while (bits >= 8)
    {
        byte_add(fdi, *fdi->track_src++);
        bits -= 8;
    }
    if (bits > 0)
    {
        i = 7;
        b = *fdi->track_src++;
        while (bits--)
        {
            bit_add(fdi, b & (1 << i));
            i--;
        }
    }
}
/* MFM-encoded data */
static void s0b(FDI* fdi)
{
    int i, bits = ((fdi->track_src[0] << 8) | fdi->track_src[1]) + 65536;
    byte b;
    fdi->track_src += 2;
    debuglog(L"s0b:bits=%d,data=%s", bits, datalog(fdi->track_src, (bits + 7) / 8));
    while (bits >= 8)
    {
        byte_add(fdi, *fdi->track_src++);
        bits -= 8;
    }
    if (bits > 0)
    {
        i = 7;
        b = *fdi->track_src++;
        while (bits--)
        {
            bit_add(fdi, b & (1 << i));
            i--;
        }
    }
}
/* MFM-decoded data */
static void s0c(FDI* fdi)
{
    int i, bits = (fdi->track_src[0] << 8) | fdi->track_src[1];
    byte b;
    fdi->track_src += 2;
    bit_drop_next(fdi);
    debuglog(L"s0c:bits=%d,data=%s", bits, datalog(fdi->track_src, (bits + 7) / 8));
    while (bits >= 8)
    {
        byte_mfm_add(fdi, *fdi->track_src++);
        bits -= 8;
    }
    if (bits > 0)
    {
        i = 7;
        b = *fdi->track_src++;
        while (bits--)
        {
            bit_mfm_add(fdi, b & (1 << i));
            i--;
        }
    }
}
/* MFM-decoded data */
static void s0d(FDI* fdi)
{
    int i, bits = ((fdi->track_src[0] << 8) | fdi->track_src[1]) + 65536;
    byte b;
    fdi->track_src += 2;
    bit_drop_next(fdi);
    debuglog(L"s0d:bits=%d,data=%s", bits, datalog(fdi->track_src, (bits + 7) / 8));
    while (bits >= 8)
    {
        byte_mfm_add(fdi, *fdi->track_src++);
        bits -= 8;
    }
    if (bits > 0)
    {
        i = 7;
        b = *fdi->track_src++;
        while (bits--)
        {
            bit_mfm_add(fdi, b & (1 << i));
            i--;
        }
    }
}

/* ***** */
/* AMIGA */
/* ***** */

/* just for testing integrity of Amiga sectors */

static void rotateonebit(byte* start, byte* end, int shift)
{
    if (shift == 0)
        return;
    while (start <= end)
    {
        start[0] <<= shift;
        start[0] |= start[1] >> (8 - shift);
        start++;
    }
}

static int check_offset;
static ushort getmfmword(byte* mbuf)
{
    uint v;

    v = (mbuf[0] << 8) | (mbuf[1] << 0);
    if (check_offset == 0)
        return v;
    v <<= 8;
    v |= mbuf[2];
    v >>= check_offset;
    return v;
}

    #define MFMMASK 0x55555555
static uint getmfmlong(byte* mbuf)
{
    return ((getmfmword(mbuf) << 16) | getmfmword(mbuf + 2)) & MFMMASK;
}

static int amiga_check_track(FDI* fdi)
{
    int i, j, secwritten = 0;
    int fwlen = fdi->out / 8;
    int length = 2 * fwlen;
    int drvsec = 11;
    uint odd, even, chksum, id, dlong;
    byte* secdata;
    byte secbuf[544];
    byte bigmfmbuf[60000];
    byte* mbuf, * mbuf2, * mend;
    TCHAR sectable[22];
    byte* raw = fdi->track_dst_buffer;
    int slabel, off;
    int ok = 1;

    memset(bigmfmbuf, 0, sizeof(bigmfmbuf));
    mbuf = bigmfmbuf;
    check_offset = 0;
    for (i = 0; i < (fdi->out + 7) / 8; i++)
        *mbuf++ = raw[i];
    off = fdi->out & 7;
    #if 1
    if (off > 0)
    {
        mbuf--;
        *mbuf &= ~((1 << (8 - off)) - 1);
    }
    j = 0;
    while (i < (fdi->out + 7) / 8 + 600)
    {
        *mbuf++ |= (raw[j] >> off) | ((raw[j + 1]) << (8 - off));
        j++;
        i++;
    }
    #endif
    mbuf = bigmfmbuf;

    memset(sectable, 0, sizeof(sectable));
    // memcpy (mbuf + fwlen, mbuf, fwlen * sizeof (ushort));
    mend = bigmfmbuf + length;
    mend -= (4 + 16 + 8 + 512);

    while (secwritten < drvsec)
    {
        int trackoffs;

        for (;;)
        {
            rotateonebit(bigmfmbuf, mend, 1);
            if (getmfmword(mbuf) == 0)
                break;
            if (secwritten == 10)
            {
                mbuf[0] = 0x44;
                mbuf[1] = 0x89;
            }
            //                  check_offset++;
            if (check_offset > 7)
            {
                check_offset = 0;
                mbuf++;
                if (mbuf >= mend || *mbuf == 0)
                    break;
            }
            if (getmfmword(mbuf) == 0x4489)
                break;
        }
        if (mbuf >= mend || *mbuf == 0)
            break;

        rotateonebit(bigmfmbuf, mend, check_offset);
        check_offset = 0;

        while (getmfmword(mbuf) == 0x4489)
            mbuf += 1 * 2;
        mbuf2 = mbuf + 8;

        odd = getmfmlong(mbuf);
        even = getmfmlong(mbuf + 2 * 2);
        mbuf += 4 * 2;
        id = (odd << 1) | even;

        trackoffs = (id & 0xff00) >> 8;
        if (trackoffs + 1 > drvsec)
        {
            outlog(L"illegal sector offset %d\n", trackoffs);
            ok = 0;
            mbuf = mbuf2;
            continue;
        }
        if ((id >> 24) != 0xff)
        {
            outlog(L"sector %d format type %02X?\n", trackoffs, id >> 24);
            ok = 0;
        }
        chksum = odd ^ even;
        slabel = 0;
        for (i = 0; i < 4; i++)
        {
            odd = getmfmlong(mbuf);
            even = getmfmlong(mbuf + 8 * 2);
            mbuf += 2 * 2;

            dlong = (odd << 1) | even;
            if (dlong)
                slabel = 1;
            chksum ^= odd ^ even;
        }
        mbuf += 8 * 2;
        odd = getmfmlong(mbuf);
        even = getmfmlong(mbuf + 2 * 2);
        mbuf += 4 * 2;
        if (((odd << 1) | even) != chksum)
        {
            outlog(L"sector %d header crc error\n", trackoffs);
            ok = 0;
            mbuf = mbuf2;
            continue;
        }
        outlog(L"sector %d header crc ok\n", trackoffs);
        if (((id & 0x00ff0000) >> 16) != (uint)fdi->current_track)
        {
            outlog(L"illegal track number %d <> %d\n", fdi->current_track, (id & 0x00ff0000) >> 16);
            ok++;
            mbuf = mbuf2;
            continue;
        }
        odd = getmfmlong(mbuf);
        even = getmfmlong(mbuf + 2 * 2);
        mbuf += 4 * 2;
        chksum = (odd << 1) | even;
        secdata = secbuf + 32;
        for (i = 0; i < 128; i++)
        {
            odd = getmfmlong(mbuf);
            even = getmfmlong(mbuf + 256 * 2);
            mbuf += 2 * 2;
            dlong = (odd << 1) | even;
            *secdata++ = (byte)(dlong >> 24);
            *secdata++ = (byte)(dlong >> 16);
            *secdata++ = (byte)(dlong >> 8);
            *secdata++ = (byte)dlong;
            chksum ^= odd ^ even;
        }
        mbuf += 256 * 2;
        if (chksum)
        {
            outlog(L"sector %d data checksum error\n", trackoffs);
            ok = 0;
        }
        else if (sectable[trackoffs])
        {
            outlog(L"sector %d already found?\n", trackoffs);
            mbuf = mbuf2;
        }
        else
        {
            outlog(L"sector %d ok\n", trackoffs);
            if (slabel)
                outlog(L"(non-empty sector header)\n");
            sectable[trackoffs] = 1;
            secwritten++;
            if (trackoffs == 9)
                mbuf += 0x228;
        }
    }
    for (i = 0; i < drvsec; i++)
    {
        if (!sectable[i])
        {
            outlog(L"sector %d missing\n", i);
            ok = 0;
        }
    }
    return ok;
}

static void amiga_data_raw(FDI* fdi, byte* secbuf, byte* crc, int len)
{
    int i;
    byte crcbuf[4];

    if (!crc)
    {
        memset(crcbuf, 0, 4);
    }
    else
    {
        memcpy(crcbuf, crc, 4);
    }
    for (i = 0; i < 4; i++)
        byte_mfm_add(fdi, crcbuf[i]);
    for (i = 0; i < len; i++)
        byte_mfm_add(fdi, secbuf[i]);
}

static void amiga_data(FDI* fdi, byte* secbuf)
{
    ushort mfmbuf[4 + 512];
    uint dodd, deven, dck;
    int i;

    for (i = 0; i < 512; i += 4)
    {
        deven = ((secbuf[i + 0] << 24) | (secbuf[i + 1] << 16)
            | (secbuf[i + 2] << 8) | (secbuf[i + 3]));
        dodd = deven >> 1;
        deven &= 0x55555555;
        dodd &= 0x55555555;
        mfmbuf[(i >> 1) + 4] = (ushort)(dodd >> 16);
        mfmbuf[(i >> 1) + 5] = (ushort)dodd;
        mfmbuf[(i >> 1) + 256 + 4] = (ushort)(deven >> 16);
        mfmbuf[(i >> 1) + 256 + 5] = (ushort)deven;
    }
    dck = 0;
    for (i = 4; i < 4 + 512; i += 2)
        dck ^= (mfmbuf[i] << 16) | mfmbuf[i + 1];
    deven = dodd = dck;
    dodd >>= 1;
    deven &= 0x55555555;
    dodd &= 0x55555555;
    mfmbuf[0] = (ushort)(dodd >> 16);
    mfmbuf[1] = (ushort)dodd;
    mfmbuf[2] = (ushort)(deven >> 16);
    mfmbuf[3] = (ushort)deven;

    for (i = 0; i < 4 + 512; i++)
        word_post_mfm_add(fdi, mfmbuf[i]);
}

static void amiga_sector_header(FDI* fdi, byte* header, byte* data, int sector, int untilgap)
{
    byte headerbuf[4], databuf[16];
    uint deven, dodd, hck;
    ushort mfmbuf[24];
    int i;

    byte_mfm_add(fdi, 0);
    byte_mfm_add(fdi, 0);
    word_add(fdi, 0x4489);
    word_add(fdi, 0x4489);
    if (header)
    {
        memcpy(headerbuf, header, 4);
    }
    else
    {
        headerbuf[0] = 0xff;
        headerbuf[1] = (byte)fdi->current_track;
        headerbuf[2] = (byte)sector;
        headerbuf[3] = (byte)untilgap;
    }
    if (data)
        memcpy(databuf, data, 16);
    else
        memset(databuf, 0, 16);

    deven = ((headerbuf[0] << 24) | (headerbuf[1] << 16)
        | (headerbuf[2] << 8) | (headerbuf[3]));
    dodd = deven >> 1;
    deven &= 0x55555555;
    dodd &= 0x55555555;
    mfmbuf[0] = (ushort)(dodd >> 16);
    mfmbuf[1] = (ushort)dodd;
    mfmbuf[2] = (ushort)(deven >> 16);
    mfmbuf[3] = (ushort)deven;
    for (i = 0; i < 16; i += 4)
    {
        deven = ((databuf[i] << 24) | (databuf[i + 1] << 16)
            | (databuf[i + 2] << 8) | (databuf[i + 3]));
        dodd = deven >> 1;
        deven &= 0x55555555;
        dodd &= 0x55555555;
        mfmbuf[(i >> 1) + 0 + 4] = (ushort)(dodd >> 16);
        mfmbuf[(i >> 1) + 0 + 5] = (ushort)dodd;
        mfmbuf[(i >> 1) + 8 + 4] = (ushort)(deven >> 16);
        mfmbuf[(i >> 1) + 8 + 5] = (ushort)deven;
    }
    hck = 0;
    for (i = 0; i < 4 + 16; i += 2)
        hck ^= (mfmbuf[i] << 16) | mfmbuf[i + 1];
    deven = dodd = hck;
    dodd >>= 1;
    deven &= 0x55555555;
    dodd &= 0x55555555;
    mfmbuf[20] = (ushort)(dodd >> 16);
    mfmbuf[21] = (ushort)dodd;
    mfmbuf[22] = (ushort)(deven >> 16);
    mfmbuf[23] = (ushort)deven;

    for (i = 0; i < 4 + 16 + 4; i++)
        word_post_mfm_add(fdi, mfmbuf[i]);
}

/* standard super-extended Amiga sector header */
static void s20(FDI* fdi)
{
    bit_drop_next(fdi);
    debuglog(L"s20:header=%s,data=%s", datalog(fdi->track_src, 4), datalog(fdi->track_src + 4, 16));
    amiga_sector_header(fdi, fdi->track_src, fdi->track_src + 4, 0, 0);
    fdi->track_src += 4 + 16;
}
/* standard extended Amiga sector header */
static void s21(FDI* fdi)
{
    bit_drop_next(fdi);
    debuglog(L"s21:header=%s", datalog(fdi->track_src, 4));
    amiga_sector_header(fdi, fdi->track_src, 0, 0, 0);
    fdi->track_src += 4;
}
/* standard Amiga sector header */
static void s22(FDI* fdi)
{
    bit_drop_next(fdi);
    debuglog(L"s22:sector=%d,untilgap=%d", fdi->track_src[0], fdi->track_src[1]);
    amiga_sector_header(fdi, 0, 0, fdi->track_src[0], fdi->track_src[1]);
    fdi->track_src += 2;
}
/* standard 512-byte, CRC-correct Amiga data */
static void s23(FDI* fdi)
{
    debuglog(L"s23:data=%s", datalog(fdi->track_src, 512));
    amiga_data(fdi, fdi->track_src);
    fdi->track_src += 512;
}
/* not-decoded, 128*2^x-byte, CRC-correct Amiga data */
static void s24(FDI* fdi)
{
    int shift = *fdi->track_src++;
    debuglog(L"s24:shift=%d,data=%s", shift, datalog(fdi->track_src, 128 << shift));
    amiga_data_raw(fdi, fdi->track_src, 0, 128 << shift);
    fdi->track_src += 128 << shift;
}
/* not-decoded, 128*2^x-byte, CRC-incorrect Amiga data */
static void s25(FDI* fdi)
{
    int shift = *fdi->track_src++;
    debuglog(L"s25:shift=%d,crc=%s,data=%s", shift, datalog(fdi->track_src, 4), datalog(fdi->track_src + 4, 128 << shift));
    amiga_data_raw(fdi, fdi->track_src + 4, fdi->track_src, 128 << shift);
    fdi->track_src += 4 + (128 << shift);
}
/* standard extended Amiga sector */
static void s26(FDI* fdi)
{
    s21(fdi);
    debuglog(L"s26:data=%s", datalog(fdi->track_src, 512));
    amiga_data(fdi, fdi->track_src);
    fdi->track_src += 512;
}
/* standard short Amiga sector */
static void s27(FDI* fdi)
{
    s22(fdi);
    debuglog(L"s27:data=%s", datalog(fdi->track_src, 512));
    amiga_data(fdi, fdi->track_src);
    fdi->track_src += 512;
}

/* *** */
/* IBM */
/* *** */

static ushort ibm_crc(byte b, int reset)
{
    static ushort crc;
    int i;

    if (reset)
        crc = 0xcdb4;
    for (i = 0; i < 8; i++)
    {
        if (crc & 0x8000)
        {
            crc <<= 1;
            if (!(b & 0x80))
                crc ^= 0x1021;
        }
        else
        {
            crc <<= 1;
            if (b & 0x80)
                crc ^= 0x1021;
        }
        b <<= 1;
    }
    return crc;
}

static void ibm_data(FDI* fdi, byte* data, byte* crc, int len)
{
    int i;
    byte crcbuf[2];
    ushort crcv = 0;

    word_add(fdi, 0x4489);
    word_add(fdi, 0x4489);
    word_add(fdi, 0x4489);
    byte_mfm_add(fdi, 0xfb);
    ibm_crc(0xfb, 1);
    for (i = 0; i < len; i++)
    {
        byte_mfm_add(fdi, data[i]);
        crcv = ibm_crc(data[i], 0);
    }
    if (!crc)
    {
        crc = crcbuf;
        crc[0] = (byte)(crcv >> 8);
        crc[1] = (byte)crcv;
    }
    byte_mfm_add(fdi, crc[0]);
    byte_mfm_add(fdi, crc[1]);
}

static void ibm_sector_header(FDI* fdi, byte* data, byte* crc, int secnum, int pre)
{
    byte secbuf[5];
    byte crcbuf[2];
    ushort crcv;
    int i;

    if (pre)
        bytes_mfm_add(fdi, 0, 12);
    word_add(fdi, 0x4489);
    word_add(fdi, 0x4489);
    word_add(fdi, 0x4489);
    secbuf[0] = 0xfe;
    if (secnum >= 0)
    {
        secbuf[1] = (byte)(fdi->current_track / 2);
        secbuf[2] = (byte)(fdi->current_track % 2);
        secbuf[3] = (byte)secnum;
        secbuf[4] = 2;
    }
    else
    {
        memcpy(secbuf + 1, data, 4);
    }
    ibm_crc(secbuf[0], 1);
    ibm_crc(secbuf[1], 0);
    ibm_crc(secbuf[2], 0);
    ibm_crc(secbuf[3], 0);
    crcv = ibm_crc(secbuf[4], 0);
    if (crc)
    {
        memcpy(crcbuf, crc, 2);
    }
    else
    {
        crcbuf[0] = (byte)(crcv >> 8);
        crcbuf[1] = (byte)crcv;
    }
    /* data */
    for (i = 0; i < 5; i++)
        byte_mfm_add(fdi, secbuf[i]);
    /* crc */
    byte_mfm_add(fdi, crcbuf[0]);
    byte_mfm_add(fdi, crcbuf[1]);
}

/* standard IBM index address mark */
static void s10(FDI* fdi)
{
    bit_drop_next(fdi);
    bytes_mfm_add(fdi, 0, 12);
    word_add(fdi, 0x5224);
    word_add(fdi, 0x5224);
    word_add(fdi, 0x5224);
    byte_mfm_add(fdi, 0xfc);
}
/* standard IBM pre-gap */
static void s11(FDI* fdi)
{
    bit_drop_next(fdi);
    bytes_mfm_add(fdi, 0x4e, 78);
    bit_dedrop(fdi);
    s10(fdi);
    bytes_mfm_add(fdi, 0x4e, 50);
}
/* standard ST pre-gap */
static void s12(FDI* fdi)
{
    bit_drop_next(fdi);
    bytes_mfm_add(fdi, 0x4e, 78);
}
/* standard extended IBM sector header */
static void s13(FDI* fdi)
{
    bit_drop_next(fdi);
    debuglog(L"s13:header=%s", datalog(fdi->track_src, 4));
    ibm_sector_header(fdi, fdi->track_src, 0, -1, 1);
    fdi->track_src += 4;
}
/* standard mini-extended IBM sector header */
static void s14(FDI* fdi)
{
    debuglog(L"s14:header=%s", datalog(fdi->track_src, 4));
    ibm_sector_header(fdi, fdi->track_src, 0, -1, 0);
    fdi->track_src += 4;
}
/* standard short IBM sector header */
static void s15(FDI* fdi)
{
    bit_drop_next(fdi);
    debuglog(L"s15:sector=%d", *fdi->track_src);
    ibm_sector_header(fdi, 0, 0, *fdi->track_src++, 1);
}
/* standard mini-short IBM sector header */
static void s16(FDI* fdi)
{
    debuglog(L"s16:track=%d", *fdi->track_src);
    ibm_sector_header(fdi, 0, 0, *fdi->track_src++, 0);
}
/* standard CRC-incorrect mini-extended IBM sector header */
static void s17(FDI* fdi)
{
    debuglog(L"s17:header=%s,crc=%s", datalog(fdi->track_src, 4), datalog(fdi->track_src + 4, 2));
    ibm_sector_header(fdi, fdi->track_src, fdi->track_src + 4, -1, 0);
    fdi->track_src += 4 + 2;
}
/* standard CRC-incorrect mini-short IBM sector header */
static void s18(FDI* fdi)
{
    debuglog(L"s18:sector=%d,header=%s", *fdi->track_src, datalog(fdi->track_src + 1, 4));
    ibm_sector_header(fdi, 0, fdi->track_src + 1, *fdi->track_src, 0);
    fdi->track_src += 1 + 4;
}
/* standard 512-byte CRC-correct IBM data */
static void s19(FDI* fdi)
{
    debuglog(L"s19:data=%s", datalog(fdi->track_src, 512));
    ibm_data(fdi, fdi->track_src, 0, 512);
    fdi->track_src += 512;
}
/* standard 128*2^x-byte-byte CRC-correct IBM data */
static void s1a(FDI* fdi)
{
    int shift = *fdi->track_src++;
    debuglog(L"s1a:shift=%d,data=%s", shift, datalog(fdi->track_src, 128 << shift));
    ibm_data(fdi, fdi->track_src, 0, 128 << shift);
    fdi->track_src += 128 << shift;
}
/* standard 128*2^x-byte-byte CRC-incorrect IBM data */
static void s1b(FDI* fdi)
{
    int shift = *fdi->track_src++;
    debuglog(L"s1b:shift=%d,crc=%s,data=%s", shift, datalog(fdi->track_src + (128 << shift), 2), datalog(fdi->track_src, 128 << shift));
    ibm_data(fdi, fdi->track_src, fdi->track_src + (128 << shift), 128 << shift);
    fdi->track_src += (128 << shift) + 2;
}
/* standard extended IBM sector */
static void s1c(FDI* fdi)
{
    int shift = fdi->track_src[3];
    s13(fdi);
    bytes_mfm_add(fdi, 0x4e, 22);
    bytes_mfm_add(fdi, 0x00, 12);
    ibm_data(fdi, fdi->track_src, 0, 128 << shift);
    fdi->track_src += 128 << shift;
}
/* standard short IBM sector */
static void s1d(FDI* fdi)
{
    s15(fdi);
    bytes_mfm_add(fdi, 0x4e, 22);
    bytes_mfm_add(fdi, 0x00, 12);
    s19(fdi);
}

/* end marker */
static void sff(FDI* fdi)
{
}

typedef void (*decode_described_track_func)(FDI*);

static decode_described_track_func decode_sectors_described_track[] =
{
    s00, s01, s02, s03, s04, dxx, dxx, dxx, s08, s09, s0a, s0b, s0c, s0d, dxx, dxx, /* 00-0F */
    s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, s1a, s1b, s1c, s1d, dxx, dxx, /* 10-1F */
    s20, s21, s22, s23, s24, s25, s26, s27, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, /* 20-2F */
    dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, /* 30-3F */
    dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, /* 40-4F */
    dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, /* 50-5F */
    dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, /* 60-6F */
    dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, /* 70-7F */
    dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, /* 80-8F */
    dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, /* 90-9F */
    dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, /* A0-AF */
    dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, /* B0-BF */
    dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, /* C0-CF */
    dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, /* D0-DF */
    dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, /* E0-EF */
    dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, dxx, sff  /* F0-FF */
};

static void track_amiga(FDI* fdi, int first_sector, int max_sector)
{
    int i;

    bit_add(fdi, 0);
    bit_drop_next(fdi);
    for (i = 0; i < max_sector; i++)
    {
        amiga_sector_header(fdi, 0, 0, first_sector, max_sector - i);
        amiga_data(fdi, fdi->track_src + first_sector * 512);
        first_sector++;
        if (first_sector >= max_sector)
            first_sector = 0;
    }
    bytes_mfm_add(fdi, 0, 260);  /* gap */
}
static void track_atari_st(FDI* fdi, int max_sector)
{
    int i, gap3 = 0;
    byte* p = fdi->track_src;

    switch (max_sector)
    {
        case 9:
            gap3 = 40;
            break;
        case 10:
            gap3 = 24;
            break;
    }
    s15(fdi);
    for (i = 0; i < max_sector; i++)
    {
        byte_mfm_add(fdi, 0x4e);
        byte_mfm_add(fdi, 0x4e);
        ibm_sector_header(fdi, 0, 0, fdi->current_track, 1);
        ibm_data(fdi, p + i * 512, 0, 512);
        bytes_mfm_add(fdi, 0x4e, gap3);
    }
    bytes_mfm_add(fdi, 0x4e, 660 - gap3);
    fdi->track_src += fdi->track_len * 256;
}
static void track_pc(FDI* fdi, int max_sector)
{
    int i, gap3;
    byte* p = fdi->track_src;

    switch (max_sector)
    {
        case 8:
            gap3 = 116;
            break;
        case 9:
            gap3 = 54;
            break;
        default:
            gap3 = 100; /* fixme */
            break;
    }
    s11(fdi);
    for (i = 0; i < max_sector; i++)
    {
        byte_mfm_add(fdi, 0x4e);
        byte_mfm_add(fdi, 0x4e);
        ibm_sector_header(fdi, 0, 0, fdi->current_track, 1);
        ibm_data(fdi, p + i * 512, 0, 512);
        bytes_mfm_add(fdi, 0x4e, gap3);
    }
    bytes_mfm_add(fdi, 0x4e, 600 - gap3);
    fdi->track_src += fdi->track_len * 256;
}

/* amiga dd */
static void track_amiga_dd(FDI* fdi)
{
    byte* p = fdi->track_src;
    track_amiga(fdi, fdi->track_len >> 4, 11);
    fdi->track_src = p + (fdi->track_len & 15) * 512;
}
/* amiga hd */
static void track_amiga_hd(FDI* fdi)
{
    byte* p = fdi->track_src;
    track_amiga(fdi, 0, 22);
    fdi->track_src = p + fdi->track_len * 256;
}
/* atari st 9 sector */
static void track_atari_st_9(FDI* fdi)
{
    track_atari_st(fdi, 9);
}
/* atari st 10 sector */
static void track_atari_st_10(FDI* fdi)
{
    track_atari_st(fdi, 10);
}
/* pc 8 sector */
static void track_pc_8(FDI* fdi)
{
    track_pc(fdi, 8);
}
/* pc 9 sector */
static void track_pc_9(FDI* fdi)
{
    track_pc(fdi, 9);
}
/* pc 15 sector */
static void track_pc_15(FDI* fdi)
{
    track_pc(fdi, 15);
}
/* pc 18 sector */
static void track_pc_18(FDI* fdi)
{
    track_pc(fdi, 18);
}
/* pc 36 sector */
static void track_pc_36(FDI* fdi)
{
    track_pc(fdi, 36);
}

typedef void (*decode_normal_track_func)(FDI*);

static decode_normal_track_func decode_normal_track[] =
{
    track_empty, /* 0 */
    track_amiga_dd, track_amiga_hd, /* 1-2 */
    track_atari_st_9, track_atari_st_10, /* 3-4 */
    track_pc_8, track_pc_9, track_pc_15, track_pc_18, track_pc_36, /* 5-9 */
    zxx, zxx, zxx, zxx, zxx /* A-F */
};

static void fix_mfm_sync(FDI* fdi)
{
    int i, pos, off1, off2, off3, mask1, mask2, mask3;

    for (i = 0; i < fdi->mfmsync_offset; i++)
    {
        pos = fdi->mfmsync_buffer[i];
        off1 = (pos - 1) >> 3;
        off2 = (pos + 1) >> 3;
        off3 = pos >> 3;
        mask1 = 1 << (7 - ((pos - 1) & 7));
        mask2 = 1 << (7 - ((pos + 1) & 7));
        mask3 = 1 << (7 - (pos & 7));
        if (!(fdi->track_dst[off1] & mask1) && !(fdi->track_dst[off2] & mask2))
            fdi->track_dst[off3] |= mask3;
        else
            fdi->track_dst[off3] &= ~mask3;
    }
}

static int handle_sectors_described_track(FDI* fdi)
{
    int oldout;
    byte* start_src = fdi->track_src;
    fdi->encoding_type = *fdi->track_src++;
    fdi->index_offset = get_u32(fdi->track_src);
    fdi->index_offset >>= 8;
    fdi->track_src += 3;
    outlog(L"sectors_described, index offset: %d\n", fdi->index_offset);

    do
    {
        fdi->track_type = *fdi->track_src++;
        outlog(L"%06X %06X %02X:", fdi->track_src - start_src + 0x200, fdi->out / 8, fdi->track_type);
        oldout = fdi->out;
        decode_sectors_described_track[fdi->track_type](fdi);
        outlog(L" %d\n", fdi->out - oldout);
        oldout = fdi->out;
        if (fdi->out < 0 || fdi->err)
        {
            outlog(L"\nin %d bytes, out %d bits\n", fdi->track_src - fdi->track_src_buffer, fdi->out);
            return -1;
        }
        if (fdi->track_src - fdi->track_src_buffer >= fdi->track_src_len)
        {
            outlog(L"source buffer overrun, previous type: %02X\n", fdi->track_type);
            return -1;
        }
    }
    while (fdi->track_type != 0xff);
    outlog(L"\n");
    fix_mfm_sync(fdi);
    return fdi->out;
}

static byte* fdi_decompress(int pulses, byte* sizep, byte* src, int* dofree)
{
    uint size = get_u24(sizep);
    uint* dst2;
    int len = size & 0x3fffff;
    byte* dst;
    int mode = size >> 22, i;

    *dofree = 0;
    if (mode == 0 && pulses * 2 > len)
        mode = 1;
    if (mode == 0)
    {
        dst2 = (uint*)src;
        dst = src;
        for (i = 0; i < pulses; i++)
        {
            *dst2++ = get_u32(src);
            src += 4;
        }
    }
    else if (mode == 1)
    {
        dst = fdi_malloc(byte, pulses * 4);
        *dofree = 1;
        fdi_decode(src, pulses, dst);
    }
    else
    {
        dst = 0;
    }
    return dst;
}

static void dumpstream(int track, byte* stream, int len)
{
    #if 0
    TCHAR name[100];
    FILE* f;

    _stprintf(name, "track_%d.raw", track);
    f = fopen(name, "wb");
    fwrite(stream, 1, len * 4, f);
    fclose(f);
    #endif
}

static int bitoffset;

static __forceinline void addbit(byte* p, int bit)
{
    int off1 = bitoffset / 8;
    int off2 = bitoffset % 8;
    p[off1] |= bit << (7 - off2);
    bitoffset++;
}

struct pulse_sample
{
    uint size;
    int number_of_bits;
};

    #define FDI_MAX_ARRAY 10 /* change this value as you want */
static int pulse_limitval = 15; /* tolerance of 15% */
static struct pulse_sample psarray[FDI_MAX_ARRAY];
static int array_index;
static uint total;
static int totaldiv;

static void init_array(uint standard_MFM_2_bit_cell_size, int nb_of_bits)
{
    int i;

    for (i = 0; i < FDI_MAX_ARRAY; i++)
    {
        psarray[i].size = standard_MFM_2_bit_cell_size; // That is (total track length / 50000) for Amiga double density
        total += psarray[i].size;
        psarray[i].number_of_bits = nb_of_bits;
        totaldiv += psarray[i].number_of_bits;
    }
    array_index = 0;
}

    #if 0

static void fdi2_decode(FDI* fdi, uint totalavg, uint* avgp, uint* minp, uint* maxp, byte* idx, int maxidx, int* indexoffsetp, int pulses, int mfm)
{
    uint adjust;
    uint adjusted_pulse;
    uint standard_MFM_2_bit_cell_size = totalavg / 50000;
    uint standard_MFM_8_bit_cell_size = totalavg / 12500;
    int real_size, i, j, eodat, outstep;
    int indexoffset = *indexoffsetp;
    byte* d = fdi->track_dst_buffer;
    ushort* pt = fdi->track_dst_buffer_timing;
    uint ref_pulse, pulse;

    /* detects a long-enough stable pulse coming just after another stable pulse */
    i = 1;
    while ((i < pulses) && ((idx[i] < maxidx)
            || (idx[i - 1] < maxidx)
            || (avgp[i] < (standard_MFM_2_bit_cell_size - (standard_MFM_2_bit_cell_size / 4)))))
        i++;
    if (i == pulses)
    {
        outlog(L"No stable and long-enough pulse in track.\n");
        return;
    }
    i--;
    eodat = i;
    adjust = 0;
    total = 0;
    totaldiv = 0;
    init_array(standard_MFM_2_bit_cell_size, 2);
    bitoffset = 0;
    ref_pulse = 0;
    outstep = 0;
    while (outstep < 2)
    {
        /* calculates the current average bitrate from previous decoded data */
        uint avg_size = (total << 3) / totaldiv; /* this is the new average size for one MFM bit */
        /* uint avg_size = (uint)((((float)total)*8.0) / ((float)totaldiv)); */
        /* you can try tighter ranges than 25%, or wider ranges. I would probably go for tighter... */
        if ((avg_size < (standard_MFM_8_bit_cell_size - (pulse_limitval * standard_MFM_8_bit_cell_size / 100))) ||
            (avg_size > (standard_MFM_8_bit_cell_size + (pulse_limitval * standard_MFM_8_bit_cell_size / 100))))
        {
            // init_array(standard_MFM_2_bit_cell_size, 2);
            avg_size = standard_MFM_8_bit_cell_size;
        }
        /* this is to prevent the average value from going too far
         * from the theoretical value, otherwise it could progressively go to (2 *
         * real value), or (real value / 2), etc. */

        /* gets the next long-enough pulse (this may require more than one pulse) */
        pulse = 0;
        while (pulse < ((avg_size / 4) - (avg_size / 16)))
        {
            int indx;
            i++;
            if (i >= pulses)
                i = 0;
            indx = idx[i];
            if (uaerand() <= (indx * RAND_MAX) / maxidx)
            {
                pulse += avgp[i] - ref_pulse;
                if (indx >= maxidx)
                    ref_pulse = 0;
                else
                    ref_pulse = avgp[i];
            }
            if (i == eodat)
                outstep++;
            if (outstep == 1 && indexoffset == i)
                *indexoffsetp = bitoffset;
        }

        /* gets the size in bits from the pulse width, considering the current average bitrate */
        adjusted_pulse = pulse;
        real_size = 0;
        while (adjusted_pulse >= avg_size)
        {
            real_size += 4;
            adjusted_pulse -= avg_size / 2;
        }
        adjusted_pulse <<= 3;
        while (adjusted_pulse >= ((avg_size * 4) + (avg_size / 4)))
        {
            real_size += 2;
            adjusted_pulse -= avg_size * 2;
        }
        if (adjusted_pulse >= ((avg_size * 3) + (avg_size / 4)))
        {
            if (adjusted_pulse <= ((avg_size * 4) - (avg_size / 4)))
            {
                if ((2 * ((adjusted_pulse >> 2) - adjust)) <= ((2 * avg_size) - (avg_size / 4)))
                    real_size += 3;
                else
                    real_size += 4;
            }
            else
                real_size += 4;
        }
        else
        {
            if (adjusted_pulse > ((avg_size * 3) - (avg_size / 4)))
            {
                real_size += 3;
            }
            else
            {
                if (adjusted_pulse >= ((avg_size * 2) + (avg_size / 4)))
                {
                    if ((2 * ((adjusted_pulse >> 2) - adjust)) < (avg_size + (avg_size / 4)))
                        real_size += 2;
                    else
                        real_size += 3;
                }
                else
                    real_size += 2;
            }
        }

        if (outstep == 1)
        {
            for (j = real_size; j > 1; j--)
                addbit(d, 0);
            addbit(d, 1);
            for (j = 0; j < real_size; j++)
                *pt++ = (ushort)(pulse / real_size);
        }

        /* prepares for the next pulse */
        adjust = ((real_size * avg_size) / 8) - pulse;
        total -= psarray[array_index].size;
        totaldiv -= psarray[array_index].number_of_bits;
        psarray[array_index].size = pulse;
        psarray[array_index].number_of_bits = real_size;
        total += pulse;
        totaldiv += real_size;
        array_index++;
        if (array_index >= FDI_MAX_ARRAY)
            array_index = 0;
    }

    fdi->out = bitoffset;
}

    #else

static void fdi2_decode(FDI* fdi, uint totalavg, uint* avgp, uint* minp, uint* maxp, byte* idx, int maxidx, int* indexoffsetp, int pulses, int mfm)
{
    uint adjust;
    uint adjusted_pulse;
    uint standard_MFM_2_bit_cell_size = totalavg / 50000;
    uint standard_MFM_8_bit_cell_size = totalavg / 12500;
    int real_size, i, j, nexti, eodat, outstep, randval;
    int indexoffset = *indexoffsetp;
    byte* d = fdi->track_dst_buffer;
    ushort* pt = fdi->track_dst_buffer_timing;
    uint ref_pulse, pulse;
    long jitter;

    /* detects a long-enough stable pulse coming just after another stable pulse */
    i = 1;
    while ((i < pulses) && ((idx[i] < maxidx)
            || (idx[i - 1] < maxidx)
            || (minp[i] < (standard_MFM_2_bit_cell_size - (standard_MFM_2_bit_cell_size / 4)))))
        i++;
    if (i == pulses)
    {
        outlog(L"FDI: No stable and long-enough pulse in track.\n");
        return;
    }
    nexti = i;
    eodat = i;
    i--;
    adjust = 0;
    total = 0;
    totaldiv = 0;
    init_array(standard_MFM_2_bit_cell_size, 1 + mfm);
    bitoffset = 0;
    ref_pulse = 0;
    jitter = 0;
    outstep = -1;
    while (outstep < 2)
    {
        /* calculates the current average bitrate from previous decoded data */
        uint avg_size = (total << (2 + mfm)) / totaldiv; /* this is the new average size for one MFM bit */
        /* uint avg_size = (uint)((((float)total)*((float)(mfm+1))*4.0) / ((float)totaldiv)); */
        /* you can try tighter ranges than 25%, or wider ranges. I would probably go for tighter... */
        if ((avg_size < (standard_MFM_8_bit_cell_size - (pulse_limitval * standard_MFM_8_bit_cell_size / 100))) ||
            (avg_size > (standard_MFM_8_bit_cell_size + (pulse_limitval * standard_MFM_8_bit_cell_size / 100))))
        {
            // init_array(standard_MFM_2_bit_cell_size, mfm + 1);
            avg_size = standard_MFM_8_bit_cell_size;
        }
        /* this	is to prevent the average value from going too far
         * from the theoretical value, otherwise it could progressively go to (2 *
         * real value), or (real value / 2), etc. */

        /* gets the next long-enough pulse (this may require more than one pulse) */
        pulse = 0;
        while (pulse < ((avg_size / 4) - (avg_size / 16)))
        {
            uint avg_pulse, min_pulse, max_pulse;
            i++;
            if (i >= pulses)
                i = 0;
            if (i == nexti)
            {
                do
                {
                    nexti++;
                    if (nexti >= pulses)
                        nexti = 0;
                }
                while (idx[nexti] < maxidx);
            }
            if (idx[i] >= maxidx)   /* stable pulse */
            {
                avg_pulse = avgp[i] - jitter;
                min_pulse = minp[i];
                max_pulse = maxp[i];
                if (jitter >= 0)
                    max_pulse -= jitter;
                else
                    min_pulse -= jitter;
                if ((maxp[nexti] - avgp[nexti]) < (avg_pulse - min_pulse))
                    min_pulse = avg_pulse - (maxp[nexti] - avgp[nexti]);
                if ((avgp[nexti] - minp[nexti]) < (max_pulse - avg_pulse))
                    max_pulse = avg_pulse + (avgp[nexti] - minp[nexti]);
                if (min_pulse < ref_pulse)
                    min_pulse = ref_pulse;
                randval = uaerand();
                if (randval < (RAND_MAX / 2))
                {
                    if (randval > (RAND_MAX / 4))
                    {
                        if (randval <= (3 * RAND_MAX / 8))
                            randval = (2 * randval) - (RAND_MAX / 4);
                        else
                            randval = (4 * randval) - RAND_MAX;
                    }
                    jitter = 0 - (randval * (avg_pulse - min_pulse)) / RAND_MAX;
                }
                else
                {
                    randval -= RAND_MAX / 2;
                    if (randval > (RAND_MAX / 4))
                    {
                        if (randval <= (3 * RAND_MAX / 8))
                            randval = (2 * randval) - (RAND_MAX / 4);
                        else
                            randval = (4 * randval) - RAND_MAX;
                    }
                    jitter = (randval * (max_pulse - avg_pulse)) / RAND_MAX;
                }
                avg_pulse += jitter;
                if ((avg_pulse < min_pulse) || (avg_pulse > max_pulse))
                {
                    outlog(L"FDI: avg_pulse outside bounds! avg=%u min=%u max=%u\n", avg_pulse, min_pulse, max_pulse);
                    outlog(L"FDI: avgp=%u (%u) minp=%u (%u) maxp=%u (%u) jitter=%d i=%d ni=%d\n",
                        avgp[i], avgp[nexti], minp[i], minp[nexti], maxp[i], maxp[nexti], jitter, i, nexti);
                }
                if (avg_pulse < ref_pulse)
                    outlog(L"FDI: avg_pulse < ref_pulse! (%u < %u)\n", avg_pulse, ref_pulse);
                pulse += avg_pulse - ref_pulse;
                ref_pulse = 0;
                if (i == eodat)
                    outstep++;
            }
            else if (uaerand() <= ((idx[i] * RAND_MAX) / maxidx))
            {
                avg_pulse = avgp[i];
                min_pulse = minp[i];
                max_pulse = maxp[i];
                randval = uaerand();
                if (randval < (RAND_MAX / 2))
                {
                    if (randval > (RAND_MAX / 4))
                    {
                        if (randval <= (3 * RAND_MAX / 8))
                            randval = (2 * randval) - (RAND_MAX / 4);
                        else
                            randval = (4 * randval) - RAND_MAX;
                    }
                    avg_pulse -= (randval * (avg_pulse - min_pulse)) / RAND_MAX;
                }
                else
                {
                    randval -= RAND_MAX / 2;
                    if (randval > (RAND_MAX / 4))
                    {
                        if (randval <= (3 * RAND_MAX / 8))
                            randval = (2 * randval) - (RAND_MAX / 4);
                        else
                            randval = (4 * randval) - RAND_MAX;
                    }
                    avg_pulse += (randval * (max_pulse - avg_pulse)) / RAND_MAX;
                }
                if ((avg_pulse > ref_pulse) && (avg_pulse < (avgp[nexti] - jitter)))
                {
                    pulse += avg_pulse - ref_pulse;
                    ref_pulse = avg_pulse;
                }
            }
            if (outstep == 1 && indexoffset == i)
                *indexoffsetp = bitoffset;
        }

        /* gets the size in bits from the pulse width, considering the current average bitrate */
        adjusted_pulse = pulse;
        real_size = 0;
        if (mfm)
        {
            while (adjusted_pulse >= avg_size)
            {
                real_size += 4;
                adjusted_pulse -= avg_size / 2;
            }
            adjusted_pulse <<= 3;
            while (adjusted_pulse >= ((avg_size * 4) + (avg_size / 4)))
            {
                real_size += 2;
                adjusted_pulse -= avg_size * 2;
            }
            if (adjusted_pulse >= ((avg_size * 3) + (avg_size / 4)))
            {
                if (adjusted_pulse <= ((avg_size * 4) - (avg_size / 4)))
                {
                    if ((2 * ((adjusted_pulse >> 2) - adjust)) <= ((2 * avg_size) - (avg_size / 4)))
                        real_size += 3;
                    else
                        real_size += 4;
                }
                else
                    real_size += 4;
            }
            else
            {
                if (adjusted_pulse > ((avg_size * 3) - (avg_size / 4)))
                {
                    real_size += 3;
                }
                else
                {
                    if (adjusted_pulse >= ((avg_size * 2) + (avg_size / 4)))
                    {
                        if ((2 * ((adjusted_pulse >> 2) - adjust)) < (avg_size + (avg_size / 4)))
                            real_size += 2;
                        else
                            real_size += 3;
                    }
                    else
                        real_size += 2;
                }
            }
        }
        else
        {
            while (adjusted_pulse >= (2 * avg_size))
            {
                real_size += 4;
                adjusted_pulse -= avg_size;
            }
            adjusted_pulse <<= 2;
            while (adjusted_pulse >= ((avg_size * 3) + (avg_size / 4)))
            {
                real_size += 2;
                adjusted_pulse -= avg_size * 2;
            }
            if (adjusted_pulse >= ((avg_size * 2) + (avg_size / 4)))
            {
                if (adjusted_pulse <= ((avg_size * 3) - (avg_size / 4)))
                {
                    if (((adjusted_pulse >> 1) - adjust) < (avg_size + (avg_size / 4)))
                        real_size += 2;
                    else
                        real_size += 3;
                }
                else
                    real_size += 3;
            }
            else
            {
                if (adjusted_pulse > ((avg_size * 2) - (avg_size / 4)))
                    real_size += 2;
                else
                {
                    if (adjusted_pulse >= (avg_size + (avg_size / 4)))
                    {
                        if (((adjusted_pulse >> 1) - adjust) <= (avg_size - (avg_size / 4)))
                            real_size++;
                        else
                            real_size += 2;
                    }
                    else
                        real_size++;
                }
            }
        }

        /* after one pass to correctly initialize the average bitrate, outputs the bits */
        if (outstep == 1)
        {
            for (j = real_size; j > 1; j--)
                addbit(d, 0);
            addbit(d, 1);
            for (j = 0; j < real_size; j++)
                *pt++ = (ushort)(pulse / real_size);
        }

        /* prepares for the next pulse */
        adjust = ((real_size * avg_size) / (4 << mfm)) - pulse;
        total -= psarray[array_index].size;
        totaldiv -= psarray[array_index].number_of_bits;
        psarray[array_index].size = pulse;
        psarray[array_index].number_of_bits = real_size;
        total += pulse;
        totaldiv += real_size;
        array_index++;
        if (array_index >= FDI_MAX_ARRAY)
            array_index = 0;
    }

    fdi->out = bitoffset;
}

    #endif

static void fdi2_celltiming(FDI* fdi, uint totalavg, int bitoffset, ushort* out)
{
    ushort* pt2, * pt;
    double avg_bit_len;
    int i;

    if (out == nullptr)
        return;
    avg_bit_len = (double)totalavg / (double)bitoffset;
    pt2 = fdi->track_dst_buffer_timing;
    pt = out;
    for (i = 0; i < bitoffset / 8; i++)
    {
        double v = (pt2[0] + pt2[1] + pt2[2] + pt2[3] + pt2[4] + pt2[5] + pt2[6] + pt2[7]) / 8.0;
        v = 1000.0 * v / avg_bit_len;
        *pt++ = (ushort)v;
        pt2 += 8;
    }
    *pt++ = out[0];
    *pt = out[0];
}

static int decode_lowlevel_track(FDI* fdi, int track, struct fdi_cache* cache)
{
    byte* p1, * d;
    uint* p2;
    uint* avgp, * minp = 0, * maxp = 0;
    byte* idxp = 0;
    uint maxidx, totalavg, weakbits;
    int i, j, len, pulses, indexoffset;
    int avg_free, min_free = 0, max_free = 0, idx_free;
    int idx_off1 = 0, idx_off2 = 0, idx_off3 = 0;

    d = fdi->track_dst;
    p1 = fdi->track_src;
    pulses = get_u32(p1);
    if (!pulses)
        return -1;
    p1 += 4;
    len = 12;
    avgp = (uint*)fdi_decompress(pulses, p1 + 0, p1 + len, &avg_free);
    dumpstream(track, (byte*)avgp, pulses);
    len += get_u24(p1 + 0) & 0x3fffff;
    if (!avgp)
        return -1;
    if (get_u24(p1 + 3) && get_u24(p1 + 6))
    {
        minp = (uint*)fdi_decompress(pulses, p1 + 3, p1 + len, &min_free);
        len += get_u24(p1 + 3) & 0x3fffff;
        maxp = (uint*)fdi_decompress(pulses, p1 + 6, p1 + len, &max_free);
        len += get_u24(p1 + 6) & 0x3fffff;
        /* Computes the real min and max values */
        for (i = 0; i < pulses; i++)
        {
            maxp[i] = avgp[i] + minp[i] - maxp[i];
            minp[i] = avgp[i] - minp[i];
        }
    }
    else
    {
        minp = avgp;
        maxp = avgp;
    }
    if (get_u24(p1 + 9))
    {
        idx_off1 = 0;
        idx_off2 = 1;
        idx_off3 = 2;
        idxp = fdi_decompress(pulses, p1 + 9, p1 + len, &idx_free);
        if (idx_free)
        {
            if (idxp[0] == 0 && idxp[1] == 0)
            {
                idx_off1 = 2;
                idx_off2 = 3;
            }
            else
            {
                idx_off1 = 1;
                idx_off2 = 0;
            }
            idx_off3 = 4;
        }
    }
    else
    {
        idxp = fdi_malloc(byte, pulses * 2);
        idx_free = 1;
        for (i = 0; i < pulses; i++)
        {
            idxp[i * 2 + 0] = 2;
            idxp[i * 2 + 1] = 0;
        }
        idxp[0] = 1;
        idxp[1] = 1;
    }

    maxidx = 0;
    indexoffset = 0;
    p1 = idxp;
    for (i = 0; i < pulses; i++)
    {
        if (p1[idx_off1] + p1[idx_off2] > maxidx)
            maxidx = p1[idx_off1] + p1[idx_off2];
        p1 += idx_off3;
    }
    p1 = idxp;
    for (i = 0; (i < pulses) && (p1[idx_off2] != 0); i++) /* falling edge, replace with idx_off1 for rising edge */
        p1 += idx_off3;
    if (i < pulses)
    {
        j = i;
        do
        {
            i++;
            p1 += idx_off3;
            if (i >= pulses)
            {
                i = 0;
                p1 = idxp;
            }
        }
        while ((i != j) && (p1[idx_off2] == 0));   /* falling edge, replace with idx_off1 for rising edge */
        if (i != j) /* index pulse detected */
        {
            while ((i != j) && (p1[idx_off1] > p1[idx_off2]))   /* falling edge, replace with "<" for rising edge */
            {
                i++;
                p1 += idx_off3;
                if (i >= pulses)
                {
                    i = 0;
                    p1 = idxp;
                }
            }
            if (i != j)
                indexoffset = i;  /* index position detected */
        }
    }
    p1 = idxp;
    p2 = avgp;
    totalavg = 0;
    weakbits = 0;
    for (i = 0; i < pulses; i++)
    {
        int sum = p1[idx_off1] + p1[idx_off2];
        if (sum >= maxidx)
        {
            totalavg += *p2;
        }
        else
        {
            weakbits++;
        }
        p2++;
        p1 += idx_off3;
        idxp[i] = sum;
    }
    len = totalavg / 100000;
    debuglog(L"totalavg=%u index=%d (%d) maxidx=%d weakbits=%d len=%d\n",
        totalavg, indexoffset, maxidx, weakbits, len);
    cache->avgp = avgp;
    cache->idxp = idxp;
    cache->minp = minp;
    cache->maxp = maxp;
    cache->avg_free = avg_free;
    cache->idx_free = idx_free;
    cache->min_free = min_free;
    cache->max_free = max_free;
    cache->totalavg = totalavg;
    cache->pulses = pulses;
    cache->maxidx = maxidx;
    cache->indexoffset = indexoffset;
    cache->weakbits = weakbits;
    cache->lowlevel = 1;

    return 1;
}

static char fdiid[] = { "Formatted Disk Image file" };
static int bit_rate_table[16] = { 125, 150, 250, 300, 500, 1000 };

void fdi2raw_header_free(FDI* fdi)
{
    int i;

    fdi_free(fdi->mfmsync_buffer);
    fdi_free(fdi->track_src_buffer);
    fdi_free(fdi->track_dst_buffer);
    fdi_free(fdi->track_dst_buffer_timing);
    for (i = 0; i < FDI_MAX_TRACKS; i++)
    {
        struct fdi_cache* c = &fdi->cache[i];
        if (c->idx_free)
            fdi_free(c->idxp);
        if (c->avg_free)
            fdi_free(c->avgp);
        if (c->min_free)
            fdi_free(c->minp);
        if (c->max_free)
            fdi_free(c->maxp);
    }
    fdi_free(fdi);
    debuglog(L"FREE: memory allocated %d\n", fdi_allocated);
}

int fdi2raw_get_last_track(FDI* fdi)
{
    return fdi->last_track;
}

int fdi2raw_get_num_sector(FDI* fdi)
{
    if (fdi->header[152] == 0x02)
        return 22;
    return 11;
}

int fdi2raw_get_last_head(FDI* fdi)
{
    return fdi->last_head;
}

int fdi2raw_get_rotation(FDI* fdi)
{
    return fdi->rotation_speed;
}

int fdi2raw_get_bit_rate(FDI* fdi)
{
    return fdi->bit_rate;
}

int fdi2raw_get_type(FDI* fdi)
{
    return fdi->disk_type;
}

int fdi2raw_get_write_protect(FDI* fdi)
{
    return fdi->write_protect;
}

FDI* fdi2raw_header(struct zfile* f)
{
    int i, offset, oldseek;
    byte type, size;
    FDI* fdi;

    debuglog(L"ALLOC: memory allocated %d\n", fdi_allocated);
    fdi = fdi_malloc(FDI, 1);
    memset(fdi, 0, sizeof(FDI));
    fdi->file = f;
    oldseek = zfile_ftell(fdi->file);
    zfile_fseek(fdi->file, 0, SEEK_SET);
    zfile_fread(fdi->header, 2048, 1, fdi->file);
    zfile_fseek(fdi->file, oldseek, SEEK_SET);
    if (memcmp(fdiid, fdi->header, strlen(fdiid)))
    {
        fdi_free(fdi);
        return nullptr;
    }
    if (fdi->header[140] != 1 && fdi->header[140] != 2)
    {
        fdi_free(fdi);
        return nullptr;
    }

    if (fdi->header[140] * 256 + fdi->header[141] >= 2 * 256 + 1)
    {
        uint crc = get_crc32(fdi->header, 508);
        uint crc2 = (fdi->header[508] << 24) | (fdi->header[509] << 16) | (fdi->header[510] << 8) | fdi->header[511];
        if (crc != crc2)
        {
            outlog(L"FDI: header checksum error\n");
            fdi_free(fdi);
            return nullptr;
        }
    }

    fdi->mfmsync_buffer = fdi_malloc(int, MAX_MFM_SYNC_BUFFER);
    fdi->track_src_buffer = fdi_malloc(byte, MAX_SRC_BUFFER);
    fdi->track_dst_buffer = fdi_malloc(byte, MAX_DST_BUFFER);
    fdi->track_dst_buffer_timing = fdi_malloc(ushort, MAX_TIMING_BUFFER / 2);

    fdi->last_track = ((fdi->header[142] << 8) + fdi->header[143]) + 1;
    fdi->last_track *= fdi->header[144] + 1;
    if (fdi->last_track >= FDI_MAX_TRACKS)
    {
        Logger::Write(L"FDI: last_track >= FDI_MAX_TRACKS (%d >= %d)\n", fdi->last_track, FDI_MAX_TRACKS);
        fdi->last_track = FDI_MAX_TRACKS - 1;
    }
    fdi->last_head = fdi->header[144];
    fdi->disk_type = fdi->header[145];
    fdi->rotation_speed = fdi->header[146] + 128;
    fdi->write_protect = fdi->header[147] & 1;
    fdi->reversed_side = (fdi->header[147] & 4) ? 1 : 0;
    outlog(L"FDI version %d.%d\n", fdi->header[140], fdi->header[141]);
    outlog(L"last_track=%d rotation_speed=%d\n", fdi->last_track, fdi->rotation_speed);

    offset = 512;
    i = fdi->last_track;
    if (i > 180)
    {
        offset += 512;
        i -= 180;
        while (i > 256)
        {
            offset += 512;
            i -= 256;
        }
    }
    for (i = 0; i < fdi->last_track; i++)
    {
        fdi->track_offsets[i] = offset;
        type = fdi->header[152 + i * 2];
        size = fdi->header[152 + i * 2 + 1];
        if (type == 1)
            offset += (size & 15) * 512;
        else if ((type & 0xc0) == 0x80)
            offset += (((type & 0x3f) << 8) | size) * 256;
        else
            offset += size * 256;
    }
    fdi->track_offsets[i] = offset;

    return fdi;
}

static int fdi2raw_loadrevolution_2(FDI* fdi, ushort* mfmbuf, ushort* tracktiming, int track, int* tracklength, int* indexoffsetp, int* multirev, int mfm)
{
    struct fdi_cache* cache = &fdi->cache[track];
    int len, i, idx;

    memset(fdi->track_dst_buffer, 0, MAX_DST_BUFFER);
    idx = cache->indexoffset;
    fdi2_decode(fdi, cache->totalavg,
        cache->avgp, cache->minp, cache->maxp, cache->idxp,
        cache->maxidx, &idx, cache->pulses, mfm);
    // fdi2_gcr_decode (fdi, totalavg, avgp, minp, maxp, idxp, idx_off1, idx_off2, idx_off3, maxidx, pulses);
    debuglog(L"track %d: nbits=%d avg len=%.2f weakbits=%d idx=%d\n",
        track, bitoffset, (double)cache->totalavg / bitoffset, cache->weakbits, cache->indexoffset);
    len = fdi->out;
    if (cache->weakbits >= 10 && multirev)
        *multirev = 1;
    *tracklength = len;

    for (i = 0; i < (len + 15) / (2 * 8); i++)
    {
        byte* data = fdi->track_dst_buffer + i * 2;
        *mfmbuf++ = 256 * *data + *(data + 1);
    }
    fdi2_celltiming(fdi, cache->totalavg, len, tracktiming);
    if (indexoffsetp)
        *indexoffsetp = idx;
    return 1;
}

int fdi2raw_loadrevolution(FDI* fdi, ushort* mfmbuf, ushort* tracktiming, int track, int* tracklength, int mfm)
{
    track ^= fdi->reversed_side;
    return fdi2raw_loadrevolution_2(fdi, mfmbuf, tracktiming, track, tracklength, 0, 0, mfm);
}

int fdi2raw_loadtrack(FDI* fdi, ushort* mfmbuf, ushort* tracktiming, int track, int* tracklength, int* indexoffsetp, int* multirev, int mfm)
{
    byte* p;
    int outlen, i /*, indexoffset = 0*/;
    struct fdi_cache* cache = &fdi->cache[track];

    track ^= fdi->reversed_side;
    if (cache->lowlevel)
        return fdi2raw_loadrevolution_2(fdi, mfmbuf, tracktiming, track, tracklength, indexoffsetp, multirev, mfm);

    fdi->err = 0;
    fdi->track_src_len = fdi->track_offsets[track + 1] - fdi->track_offsets[track];
    zfile_fseek(fdi->file, fdi->track_offsets[track], SEEK_SET);
    zfile_fread(fdi->track_src_buffer, fdi->track_src_len, 1, fdi->file);
    memset(fdi->track_dst_buffer, 0, MAX_DST_BUFFER);
    fdi->track_dst_buffer_timing[0] = 0;

    fdi->current_track = track;
    fdi->track_src = fdi->track_src_buffer;
    fdi->track_dst = fdi->track_dst_buffer;
    p = fdi->header + 152 + fdi->current_track * 2;
    fdi->track_type = *p++;
    fdi->track_len = *p++;
    fdi->bit_rate = 0;
    fdi->out = 0;
    fdi->mfmsync_offset = 0;

    if ((fdi->track_type & 0xf0) == 0xf0 || (fdi->track_type & 0xf0) == 0xe0)
        fdi->bit_rate = bit_rate_table[fdi->track_type & 0x0f];
    else
        fdi->bit_rate = 250;

    debuglog(L"track %d: srclen: %d track_type: %02X, bitrate: %d\n",
        fdi->current_track, fdi->track_src_len, fdi->track_type, fdi->bit_rate);

    if ((fdi->track_type & 0xc0) == 0x80)
    {
        outlen = decode_lowlevel_track(fdi, track, cache);
    }
    else if ((fdi->track_type & 0xf0) == 0xf0)
    {
        outlen = decode_raw_track(fdi);
    }
    else if ((fdi->track_type & 0xf0) == 0xe0)
    {
        outlen = handle_sectors_described_track(fdi);
    }
    else if ((fdi->track_type & 0xf0))
    {
        zxx(fdi);
        outlen = -1;
    }
    else if (fdi->track_type < 0x10)
    {
        decode_normal_track[fdi->track_type](fdi);
        fix_mfm_sync(fdi);
        outlen = fdi->out;
    }
    else
    {
        zxx(fdi);
        outlen = -1;
    }

    //  amiga_check_track (fdi);

    if (fdi->err)
        return 0;

    if (outlen > 0)
    {
        if (cache->lowlevel)
            return fdi2raw_loadrevolution_2(fdi, mfmbuf, tracktiming, track, tracklength, indexoffsetp, multirev, mfm);
        *tracklength = fdi->out;
        for (i = 0; i < ((*tracklength) + 15) / (2 * 8); i++)
        {
            byte* data = fdi->track_dst_buffer + i * 2;
            *mfmbuf++ = 256 * *data + *(data + 1);
        }
    }
    return outlen;
}

#endif